package com.xumingxun.database_sync.sync.impl;

import com.alibaba.fastjson.JSON;
import com.xumingxun.database_sync.domain.Config;
import com.xumingxun.database_sync.domain.Source;
import com.xumingxun.database_sync.domain.Target;
import com.xumingxun.database_sync.sync.Sync;
import com.xumingxun.database_sync.util.DatabaseUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.*;
import java.util.stream.Collectors;

import static com.xumingxun.database_sync.util.DatabaseUtil.*;

@Component(value = "databaseSync")
@Slf4j
public class DatabaseSync implements Sync {

    @SneakyThrows
    public void run(Source source, Target target, Config config) {
        List<String> tables=getTables(source,target);
        log.info("同步中的表：【{}】", JSON.toJSONString(tables));

        log.info("同步表结构开始");
        syncTables(tables,source,target);
        log.info("同步表结构结束");

        log.info("同步数据开始");
        syncRows(tables,source,target, config);
        log.info("同步数据结束");

        DatabaseUtil.closeConnection();
    }

    @SneakyThrows
    private void syncRows(List<String> tables,Source source,Target target, Config config) {

        Statement sourceStmt=createStatement(source.getUrl(), source.getUsername(),source.getPassword());
        Statement targetStmt=createStatement(target.getUrl(), target.getUsername(),target.getPassword());

        update(targetStmt,"SET FOREIGN_KEY_CHECKS = 0");
        for(String table:tables){
            log.info("同步表{}开始",table);

            List<Map<String,Object>> columns= query(sourceStmt, String.format("show columns from `%s`", table));



            int pageNum=0;
            int pageSize=config.getDefaultPageSize();
            List<List<Object>> rows;
            do{

                rows=queryList(sourceStmt, String.format("select * from `%s` limit %d,%d", table,(pageNum*pageSize),pageSize));
                if(rows.size()==0){
                    break;
                }

                StringBuilder sb=new StringBuilder("(");
                for(int i=0;i<columns.size();i++){
                    sb.append(",?");
                }
                sb.append(") ");
                sb.deleteCharAt(1);
                String fillStr=sb.toString();

                StringBuilder valuesSB=new StringBuilder();
                for(int i=0;i<rows.size();i++){
                    valuesSB.append(","+fillStr);
                }
                valuesSB.deleteCharAt(0);

                PreparedStatement pstmt= createPreparedStatement(target.getUrl(), target.getUsername(),target.getPassword(), String.format("insert into `%s` values %s", table,valuesSB.toString()));


                log.info("同步表{}开始，offset-{},size-{}",table,pageNum*pageSize,rows.size());
                int pIndex=1;
                for (int rowI=0;rowI<rows.size();rowI++) {
                    List<Object> row = rows.get(rowI);
                    for(int i=0;i<row.size();i++){
                        pstmt.setObject(pIndex++,row.get(i));
                    }
                }

                pstmt.execute();
                closeStatement(pstmt);

                log.info("同步表{}结束，offset-{},size-{}",table,pageNum*pageSize,rows.size());
                pageNum++;
            }while (rows.size()>0);



            log.info("同步表{}结束",table);
        }

        update(targetStmt,"SET FOREIGN_KEY_CHECKS = 1");


        closeStatement(sourceStmt);
        closeStatement(targetStmt);
    }

    private List<String> getTables(Source source,Target target){
        Statement sourceStmt=createStatement(source.getUrl(), source.getUsername(),source.getPassword());
        Statement targetStmt=createStatement(target.getUrl(), target.getUsername(),target.getPassword());

        List<String> tables=new ArrayList<>();
        if(source.getTables().equals("*")){
            List<Map<String,Object>> rows=query(sourceStmt,"show tables");
            tables= rows.stream().map(item->item.values().iterator().next().toString()).collect(Collectors.toList());
        }else{
            tables= Arrays.stream(source.getTables().split(",")).map(item->item.trim()).collect(Collectors.toList());
        }

        if(StringUtils.hasText(source.getExcludes())){
            List<String> excludes = Arrays.stream(source.getExcludes().split(",")).map(item->item.trim()).collect(Collectors.toList());
            tables.removeAll(excludes);
        }
        closeStatement(sourceStmt);
        closeStatement(targetStmt);

        return tables;
    }

    private void syncTables(List<String> tables,Source source ,Target target){

        Statement sourceStmt=createStatement(source.getUrl(), source.getUsername(),source.getPassword());
        Statement targetStmt=createStatement(target.getUrl(), target.getUsername(),target.getPassword());

        update(targetStmt,"SET FOREIGN_KEY_CHECKS = 0");

        List<String> targetExcludes=Arrays.stream(target.getExcludes().split(",")).map(item->item.trim()).collect(Collectors.toList());


        for(String table:tables){
            if(targetExcludes.contains(table)){
                continue;
            }

            update(targetStmt, String.format("drop table if EXISTS `%s`", table));
            List<Map<String,Object>> createSqls= query(sourceStmt, String.format("show create table `%s`", table));
            String sql=createSqls.get(0).values().toArray()[1].toString();
            update(targetStmt,sql);
        }
        update(targetStmt,"SET FOREIGN_KEY_CHECKS = 1");

        closeStatement(sourceStmt);
        closeStatement(targetStmt);
    }
}
